﻿import { IVec2 } from "../../util/Defined";
import { MathUtil } from "../../util/MathUtil";
import { Vec2 } from "./Vec2";

/**
 * 变换矩阵，它确定如何将点从一个坐标空间映射到另一个坐标空间
 */
export class Matrix {

    /**缩放或旋转图像时影响像素沿 x 轴定位的值*/
    a: number;
    /**旋转或倾斜图像时影响像素沿 y 轴定位的值*/
    b: number;
    /**旋转或倾斜图像时影响像素沿 x 轴定位的值*/
    c: number;
    /**缩放或旋转图像时影响像素沿 y 轴定位的值*/
    d: number;
    /**沿 x 轴平移每个点的距离*/
    tx: number;
    /**沿 y 轴平移每个点的距离*/
    ty: number;

    /**
     * 使用指定参数创建新的 Mat 对象
     * @param a 缩放或旋转图像时影响像素沿 x 轴定位的值
     * @param b 旋转或倾斜图像时影响像素沿 y 轴定位的值
     * @param c 旋转或倾斜图像时影响像素沿 x 轴定位的值
     * @param d 缩放或旋转图像时影响像素沿 y 轴定位的值
     * @param tx 沿 x 轴平移每个点的距离
     * @param ty 沿 y 轴平移每个点的距离
     */
    constructor(a = 1, b = 0, c = 0, d = 1, tx = 0, ty = 0) {
        this.set(a, b, c, d, tx, ty);
    }

    /**
     *  将 mat 的成员设置为指定值
     * @param a 缩放或旋转图像时影响像素沿 x 轴定位的值
     * @param b 旋转或倾斜图像时影响像素沿 y 轴定位的值
     * @param c 旋转或倾斜图像时影响像素沿 x 轴定位的值
     * @param d 缩放或旋转图像时影响像素沿 y 轴定位的值
     * @param tx 沿 x 轴平移每个点的距离
     * @param ty 沿 y 轴平移每个点的距离
     */
    set(a: number, b: number, c: number, d: number, tx: number, ty: number) {
        this.a = a, this.b = b, this.c = c, this.d = d, this.tx = tx, this.ty = ty;
        return this;
    }

    /**
     * 将本矩阵设置为单位矩阵
     */
    identity(): Matrix {
        return this.set(1, 0, 0, 1, 0, 0);
    }


    /**
     * 复制当前 mat 对象中的所有矩阵数据 创建新的mat对象
     */
    copy(): Matrix {
        return new Matrix(this.a, this.b, this.c, this.d, this.tx, this.ty);
    }

    /**
     * 设置沿 x 、y 轴平移每个点的距离
     * @param x 沿 x 轴平移每个点的距离
     * @param y 沿 y 轴平移每个点的距离
     */
    setTranslate(x: number, y: number) {
        this.tx = x;
        this.ty = y;
        return this;
    }

    /**
     * 沿 x 和 y 轴平移矩阵，平移的变化量由 x 和 y 参数指定
     * @param x 沿 x 轴向右移动的量（以像素为单位）
     * @param y 沿 y 轴向下移动的量（以像素为单位）
     */
    translate(x: number, y: number) {
        this.tx += x;
        this.ty += y;
        return this;
    }

    /**
     * 设置旋转值
     * @param rad 弧度
     */
    setRotation(rad: number) {
        this.a = Math.cos(rad);
        this.b = Math.sin(rad);
        this.c = -Math.sin(rad);
        this.d = Math.cos(rad);
        return this;
    }

    /**
     * 设置旋转值
     * @param angle 角度
     */
    setRotationAngle(angle: number) {
        return this.setRotation(MathUtil.ang2Rad(angle));
    }

    /**
     * 设置旋转值和缩放值
     * @param rad 弧度
     * @param scaleX x缩放
     * @param scaleY y缩放
     */
    setScaleRotation(rad: number, scaleX: number, scaleY: number) {
        this.a = scaleX * Math.cos(rad);
        this.b = scaleX * Math.sin(rad);
        this.c = scaleY * -Math.sin(rad);
        this.d = scaleY * Math.cos(rad);
        return this;
    }

    /**
     * 设置旋转值和缩放值
     * @param angle 角度
     * @param scaleX x缩放
     * @param scaleY y缩放
     */
    setScaleRotationAngle(rad: number, scaleX: number, scaleY: number) {
        return this.setScaleRotation(MathUtil.ang2Rad(rad), scaleX, scaleY);
    }

    /**
     * 对矩阵应用缩放转换
     * @param x 用于沿 x 轴缩放对象的乘数
     * @param y 用于沿 y 轴缩放对象的乘数
     */
    scale(x: number, y: number) {
        this.a *= x;
        this.d *= y;
        this.c *= x;
        this.b *= y;
        this.tx *= x;
        this.ty *= y;
        return this;
    }

    /**
     * 对 mat 对象应用旋转转换
     * @param rad 弧度
     */
    rotate(rad: number) {
        const cos = Math.cos(rad);
        const sin = Math.sin(rad);
        const a1 = this.a;
        const c1 = this.c;
        const tx1 = this.tx;

        this.a = a1 * cos - this.b * sin;
        this.b = a1 * sin + this.b * cos;
        this.c = c1 * cos - this.d * sin;
        this.d = c1 * sin + this.d * cos;
        this.tx = tx1 * cos - this.ty * sin;
        this.ty = tx1 * sin + this.ty * cos;
        return this;
    }

    /**
     * 对 mat 对象应用倾斜转换
     * @param x 沿着 X 轴的 2D 倾斜弧度
     * @param y 沿着 Y 轴的 2D 倾斜弧度
     */
    skew(x: number, y: number) {
        const tanX = Math.tan(x);
        const tanY = Math.tan(y);
        const a1 = this.a;
        const b1 = this.b;
        this.a += tanY * this.c;
        this.b += tanY * this.d;
        this.c += tanX * a1;
        this.d += tanX * b1;
        return this;
    }

    /**
     * 对指定的点应用当前矩阵的逆转化并返回此点
     * @param out 待转化的点 Point 对象
     */
    invertTransformPoint<T extends IVec2>(out: T) {
        const a1 = this.a;
        const b1 = this.b;
        const c1 = this.c;
        const d1 = this.d;
        const tx1 = this.tx;
        const n = a1 * d1 - b1 * c1;

        const a2 = d1 / n;
        const b2 = -b1 / n;
        const c2 = -c1 / n;
        const d2 = a1 / n;
        const tx2 = (c1 * this.ty - d1 * tx1) / n;
        const ty2 = -(a1 * this.ty - b1 * tx1) / n;
        return Vec2.setPos(out, a2 * out.x + c2 * out.y + tx2, b2 * out.x + d2 * out.y + ty2);
    }

    /**
     * 将 mat 对象表示的几何转换应用于指定点
     * @param out 用来设定输出结果的点
     */
    transformPoint<T extends IVec2>(out: T) {
        return Vec2.setPos(out, this.a * out.x + this.c * out.y + this.tx, this.b * out.x + this.d * out.y + this.ty);
    }

    /**
     * 将 mat 对象表示的几何转换应用于指定点，忽略tx、ty
     * @param out 用来设定输出结果的点
     */
    transformPointN<T extends IVec2>(out: T) {
        return Vec2.setPos(out, this.a * out.x + this.c * out.y, this.b * out.x + this.d * out.y)
    }

    /**
     * 获取 X 轴缩放值
     */
    getScaleX() {
        return this.b === 0 ? this.a : Math.sqrt(this.a * this.a + this.b * this.b);
    }

    /**
     * 获取 Y 轴缩放值
     */
    getScaleY() {
        return this.c === 0 ? this.d : Math.sqrt(this.c * this.c + this.d * this.d);
    }

    /**
     * 执行原始矩阵的逆转换
     */
    invert() {
        const a1 = this.a;
        const b1 = this.b;
        const c1 = this.c;
        const d1 = this.d;
        const tx1 = this.tx;
        const n = a1 * d1 - b1 * c1;
        this.a = d1 / n;
        this.b = -b1 / n;
        this.c = -c1 / n;
        this.d = a1 / n;
        this.tx = (c1 * this.ty - d1 * tx1) / n;
        this.ty = -(a1 * this.ty - b1 * tx1) / n;
        return this;
    }

    /**
     * 将指定矩阵与当前矩阵连接，从而将这两个矩阵的几何效果有效地结合在一起
     * @param mat 要连接到源矩阵的矩阵
     */
    concat(mat: Readonly<Matrix>) {
        const a = this.a;
        const c = this.c;
        const tx = this.tx;
        this.a = a * mat.a + this.b * mat.c;
        this.b = a * mat.b + this.b * mat.d;
        this.c = c * mat.a + this.d * mat.c;
        this.d = c * mat.b + this.d * mat.d;
        this.tx = tx * mat.a + this.ty * mat.c + mat.tx;
        this.ty = tx * mat.b + this.ty * mat.d + mat.ty;
        return this;
    }

    /**
     * 将指定的两个矩阵相乘后的结果赋值给指定的输出对象
     * @param m1 矩阵一
     * @param m2 矩阵二
     * @param out 输出对象
     */
    static mul(m1: Readonly<Matrix>, m2: Readonly<Matrix>, out: Matrix) {
        const aa = m1.a, ab = m1.b, ac = m1.c, ad = m1.d, atx = m1.tx, aty = m1.ty;
        const ba = m2.a, bb = m2.b, bc = m2.c, bd = m2.d, btx = m2.tx, bty = m2.ty;
        if (bb !== 0 || bc !== 0) {
            out.a = aa * ba + ab * bc;
            out.b = aa * bb + ab * bd;
            out.c = ac * ba + ad * bc;
            out.d = ac * bb + ad * bd;
            out.tx = ba * atx + bc * aty + btx;
            out.ty = bb * atx + bd * aty + bty;
        } else {
            out.a = aa * ba;
            out.b = ab * bd;
            out.c = ac * ba;
            out.d = ad * bd;
            out.tx = ba * atx + btx;
            out.ty = bd * aty + bty;
        }
        return out;
    }

    /**
     * 将指定的两个矩阵相乘，结果赋值给指定的输出数组，长度为16
     * @param m1 矩阵一
     * @param m2 矩阵二
     * @param out 输出对象Array
     */
    static mul16(m1: Readonly<Matrix>, m2: Readonly<Matrix>, out: any[]) {
        const aa = m1.a, ab = m1.b, ac = m1.c, ad = m1.d, atx = m1.tx, aty = m1.ty;
        const ba = m2.a, bb = m2.b, bc = m2.c, bd = m2.d, btx = m2.tx, bty = m2.ty;
        if (bb !== 0 || bc !== 0) {
            out[0] = aa * ba + ab * bc;
            out[1] = aa * bb + ab * bd;
            out[4] = ac * ba + ad * bc;
            out[5] = ac * bb + ad * bd;
            out[12] = ba * atx + bc * aty + btx;
            out[13] = bb * atx + bd * aty + bty;
        } else {
            out[0] = aa * ba;
            out[1] = ab * bd;
            out[4] = ac * ba;
            out[5] = ad * bd;
            out[12] = ba * atx + btx;
            out[13] = bd * aty + bty;
        }
        return out;
    }

    /**
     * 对矩阵应用缩放转换反向相乘
     * @param x 用于沿 x 轴缩放对象的乘数
     * @param y 用于沿 y 轴缩放对象的乘数
     */
    scaleEx(x: number, y: number) {
        const ba = this.a, bb = this.b, bc = this.c, bd = this.d;
        if (bb !== 0 || bc !== 0) {
            this.a = x * ba;
            this.b = x * bb;
            this.c = y * bc;
            this.d = y * bd;
        } else {
            this.a = x * ba;
            this.b = 0 * bd;
            this.c = 0 * ba;
            this.d = y * bd;
        }
    }

    /**
     * 对 mat 对象应用旋转转换反向相乘
     * @param rad 弧度
     */
    rotateEx(rad: number) {
        const cos = Math.cos(rad);
        const sin = Math.sin(rad);
        const ba = this.a, bb = this.b, bc = this.c, bd = this.d;
        if (bb !== 0 || bc !== 0) {
            this.a = cos * ba + sin * bc;
            this.b = cos * bb + sin * bd;
            this.c = -sin * ba + cos * bc;
            this.d = -sin * bb + cos * bd;
        } else {
            this.a = cos * ba;
            this.b = sin * bd;
            this.c = -sin * ba;
            this.d = cos * bd;
        }
    }

}